#ifndef HDR_TURNTABLECONTROLLER
#define HDR_TURNTABLECONTROLLER

#include <Eigen/Core>
#include <Eigen/Geometry>

/// @brief Implements mouse control of a 3D view.
struct TurntableController {
	enum DragType {
		DRAG_NULL, //< Not currently dragging.

		DRAG_ROTATE, //< Rotate around the look-at point.
		DRAG_LOOKAT, //< Move the look-at point perpendicular to the view direction.
		DRAG_LOOKAT_Z //< Move the look-at point parallel to the view direction.
	};

	TurntableController();

	/// @brief Reset the viewpoint to its default.
	void reset();

	/// @brief Begin a rotation drag (rotating the view around the look-at point).
	void beginDragRotate(const Eigen::Vector2f& mousePt);

	/// @brief Begin a movement drag (moving the look-at point perpendicular to the viewing direction).
	void beginDragLookat(const Eigen::Vector2f& mousePt);

	/// @brief Begin a walk drag (moving the look-at point parallel to the viewing direction).
	void beginDragLookatZ(const Eigen::Vector2f& mousePt);

	/// @brief Begin a mouse dragging operation.
	void beginDrag(const Eigen::Vector2f& mousePt, DragType type);

	/// @brief Update a mouse dragging operation.
	void updateDrag(const Eigen::Vector2f& mousePt);

	/// @brief End a mouse dragging operation.
	void endDrag(const Eigen::Vector2f& mousePt);

	/// @brief Cancels a mouse dragging operation.
	void cancelDrag();

	/// @brief Is the turntable currently being manipulated?
	bool isDragging() const;

	/// @brief Apply a scroll-wheel change to the view range.
	void scrollRange(int wheelTicks);

	/// @brief Set the view to fixed values.
	void setView(double azimuth, double elevation, double range, const Eigen::Vector3d& lookAt);

	/// @brief Get the current azimuth (not including in-progress drag effects).
	double getAzimuth() const;

	/// @brief Get the current elevation (not including in-progress drag effects).
	double getElevation() const;

	/// @brief Get the current range (not including in-progress drag effects).
	double getRange() const;

	/// @brief Get the current look-at point (not including in-progress drag effects).
	Eigen::Vector3d getLookAtPoint() const;

	/// @brief Get the current view (including any in-progress drags).
	Eigen::Isometry3d getWorldToView() const;
	Eigen::Quaterniond getViewRotation() const;
private:
	void updateWorldToView();
	void updateWorldToView(double az, double el, double rg, const Eigen::Vector3d& pivot);
	Eigen::Quaterniond calculateRotation(double az, double el) const;

	void interpretDragRotate(const Eigen::Vector2f& delta, double& az, double& el) const;
	void interpretDragLookAt(const Eigen::Vector2f& delta, Eigen::Vector3d& pivot) const;
	void interpretDragLookAtZ(const Eigen::Vector2f& delta, Eigen::Vector3d& pivot) const;

	double azimuth;
	double elevation;
	double range;
	Eigen::Vector3d lookAt;

	int dragging;
	Eigen::Vector2f startPt;
	Eigen::Vector2f curPt;

	Eigen::Quaterniond viewRotation;
	Eigen::Isometry3d worldToView;
};

inline bool TurntableController::isDragging() const
{ return (dragging != DRAG_NULL); }

inline double TurntableController::getAzimuth() const
{ return azimuth; }
inline double TurntableController::getElevation() const
{ return elevation; }
inline double TurntableController::getRange() const
{ return range; }
inline Eigen::Vector3d TurntableController::getLookAtPoint() const
{ return lookAt; }

inline Eigen::Isometry3d TurntableController::getWorldToView() const
{ return worldToView; }

inline Eigen::Quaterniond TurntableController::getViewRotation() const
{ return viewRotation; }

#endif
